package org.chispa.api.query;

import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Scanner;

import org.chispa.api.falsepos.NumberFilter;
import org.chispa.api.falsepos.StringFilter;
import org.chispa.api.falsepos.StringFilterMode;
import org.chispa.api.parsers.ParserListener;
import org.chispa.api.parsers.ParserState;
import org.chispa.api.parsers.StateRepository;
import org.chispa.api.processor.QueryProcessor;
import org.chispa.api.util.BadSyntaxException;
import org.chispa.api.util.BinningNumber;
import org.chispa.api.util.ExpressionLanguage;
import org.chispa.api.util.ExpressionLanguage.Type;
import org.chispa.api.util.NoSuitableVisitorException;
import org.chispa.api.util.RelationalSymbol;
import org.chispa.api.visitors.QueryVisitor;

import com.google.common.annotations.VisibleForTesting;

@VisibleForTesting
public class QuerySparqlWrapper implements QueryWrapper<String,Long>,ParserListener<Long> {
	
	protected String query;
	protected String original;
	private QueryVisitor<String,Long> visitor;
	private QueryProcessor<String,String> proc;
	
	QuerySparqlWrapper(String query,QueryProcessor<String,String> proc)
	{
		this.query=query;
		this.proc=proc;
	}

	@Override
	public synchronized String accept(QueryVisitor<String,Long> visitor) throws NoSuitableVisitorException {
		//Normalize the query text
		this.visitor=visitor;
		query=proc.preprocess();
		original=query;
		Scanner scan=new Scanner(query);
		StateRepository repo=StateRepository.getInstance();
		ParserState state=repo.getInitialState(scan);
		state.setListener(this);
	
		while(scan.hasNext())
		{ 
			try
			{
				state=state.validateState();
			}
			catch (NoSuchElementException e)
			{
				return state.flush();
			} catch (BadSyntaxException e) {
				System.err.println(e.getLocalizedMessage());
				return null;
			}
		}
		repo.reset();
		return state.flush();
	}
	
	private boolean checkNumParam(Map<String, String> varValues,List<String> bindingNames,Scanner scan, boolean ok,String currParam)
	{
		String token=scan.next();

		if(token.equals(")"))
		{	
			return ok;		
		}
		
		else if(token.equals("&&"))
		{
			return ok&&checkNumParam(varValues,bindingNames,scan,ok,currParam);
		}
		
		else if(token.equals("||"))
		{
			return ok||checkNumParam(varValues,bindingNames,scan,ok,currParam);
		}
		else if(!token.equals("("))
		{
			RelationalSymbol rel=RelationalSymbol.fromString(scan.next());
			if(rel!=null)
			{
				String right=scan.next();
				if(token.equals("?"+currParam) || token.equals("$"+currParam)
						|| right.equals("?"+currParam) || right.equals("$"+currParam))
				{
					Double leftNum=Double.parseDouble(varValues.get(token.substring(1))==null?token:varValues.get(token.substring(1)));
					Double rightNum=Double.parseDouble(varValues.get(right.substring(1))==null?right:varValues.get(right.substring(1)));
					ok= new NumberFilter(leftNum, rightNum, rel).isTrue();
				}
			}
		}
		return checkNumParam(varValues,bindingNames,scan,ok,currParam);
		
	}
	
	private boolean checkStringParam(String value,Scanner scan,boolean ok)
	{
		String token=scan.next();

		if(token.equals(")"))
		{	
			return ok;		
		}
		
		else if(token.equals("LIKE"))
		{
			token=closeLiteral(scan);
			ok=new StringFilter(token,StringFilterMode.LIKE).check(value);
		}
		
		else if(token.equals("NOT"))
		{
			scan.next();
			token=closeLiteral(scan);
			ok=new StringFilter(token,StringFilterMode.NOT_LIKE).check(value);
		}
		
		else if(token.equals("&&"))
		{
			return ok&&checkStringParam(value,scan,ok);
		}
		
		else if(token.equals("||"))
		{
			return ok||checkStringParam(value,scan,ok);
		}
		else if(!token.equals("("))
		{
			token=closeLiteral(scan);
			ok=new StringFilter(token,StringFilterMode.FULL).check(value);
		}
		return checkStringParam(value,scan,ok);
	}
	
	private String closeLiteral(Scanner scan)
	{
		String token=scan.next();
		if(ExpressionLanguage.isUnclosedLiteral(token))
		{
			String text;
			while(scan.hasNext())
			{
				text=scan.next();
				token=token+" "+text;
				if(ExpressionLanguage.isUnclosedLiteral(text))
					return token;
			}
			
		}
		return token;
	}
	
	@Override
	public String onLogicalHash(String text) throws NoSuitableVisitorException, BadSyntaxException {

		ParamWrapper<String, String,String>wrap=new SimpleExprParamWrapper(text);
		return visitor.visitStringHash(wrap);
	}

	@Override
	public String onSimpleHash(String text) throws NoSuitableVisitorException, BadSyntaxException {

		ParamWrapper<String,String, String>wrap=new SimpleParamWrapper<String,String,String>(text);
		return visitor.visitStringHash(wrap);
	}
	
	
	@Override
	public String onEncryptString(String text)
			throws NoSuitableVisitorException {
		ParamWrapper<String,String,String>wrap=new SimpleParamWrapper<String,String,String>(text);
		return visitor.visitString(wrap);
	}

	@Override
	public String onEncryptNumber(BinningNumber number,Type type)
			throws NoSuitableVisitorException {
		ParamWrapper<BinningNumber,Long,String>wrap=new SimpleParamWrapper<BinningNumber,Long,String>(number);
		if(type.equals(Type.FLOAT))
			return visitor.visitFloat(wrap);
		return visitor.visitInt(wrap);
	}


	@Override
	public Long onSimpleHash(BinningNumber number, Type type)
			throws NoSuitableVisitorException, BadSyntaxException {
		
		ParamWrapper<BinningNumber, Long, String> wrap=new SimpleParamWrapper<BinningNumber,Long,String>(number);
		if(type.equals(Type.FLOAT))
		{
			return visitor.visitFloatHash(wrap);
		}
		return visitor.visitIntHash(wrap);
	}

	@Override
	public String onLogicalNumberHash(String text, Type type,Map<String,String>params)
			throws NoSuitableVisitorException, BadSyntaxException {
		ParamWrapper<String, String, String> wrap=new SimpleExprParamWrapper(text,params);
		if(type.equals(Type.FLOAT))
		{
			return visitor.visitFloatHashExpr(wrap);
		}
		return visitor.visitIntHashExpr(wrap);
	}

	@Override
	public String onDecryptText(String enc) throws NoSuitableVisitorException {
		ParamWrapper<String,String,String>wrap=new SimpleParamWrapper<String,String,String>(enc);
		return visitor.visitDecryptNum(wrap);
	}

	public boolean isSecure(String paramName) {
		return proc.isSecure("?"+paramName) || proc.isSecure("$"+paramName);
	}

	public boolean matches(Map<String, String> varValues,List<String> bindingNames) {

		for(String paramName:bindingNames)
		{
			String value=varValues.get(paramName);
			Scanner scan=new Scanner(original);
			while(scan.hasNext())
			{
				String token=scan.next();
				if(ExpressionLanguage.matchesHash(token))
				{
					scan.next();
					if(token.endsWith(ExpressionLanguage.STRING_NAME))
					{
						String param=scan.next();
						if(param.equals("?"+paramName) || param.equals("$"+paramName))
						{
							scan.next();
							if (!checkStringParam(value,scan,true))
								return false;

						}
					}
					
					else
					{
						if(!checkNumParam(varValues, bindingNames, scan, true, paramName))
							return false;
					}
				}
			}
		}
		return true;
	}

}
